package gek.uni.diplom.ocl.example.visitors;

import java.util.List;

import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ecore.internal.OCLFactoryImpl;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.IntegerLiteralExp;
import org.eclipse.ocl.expressions.IterateExp;
import org.eclipse.ocl.expressions.LiteralExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.expressions.RealLiteralExp;
import org.eclipse.ocl.expressions.StringLiteralExp;
import org.eclipse.ocl.expressions.TypeExp;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.types.PrimitiveType;
import org.eclipse.ocl.utilities.AbstractVisitor;
import org.eclipse.ocl.utilities.PredefinedType;

/**
 * TODO праметризировать {@link AbstractVisitor} типом OperationCallExp[]
 * 
 * @author arkady
 * 
 * @param <C>
 * @param <O>
 * @param <P>
 * @param <EL>
 * @param <PM>
 * @param <S>
 * @param <COA>
 * @param <SSA>
 * @param <CT>
 */
public class SplitterVisitor<C, O, P, EL, PM, S, COA, SSA, CT> extends
    AbstractVisitor<OCLExpression<C>[], C, O, P, EL, PM, S, COA, SSA, CT> {

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitIntegerLiteralExp(
      IntegerLiteralExp<C> literalExp) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = null;
    res[1] = literalExp;
    return res;
  }

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitRealLiteralExp(RealLiteralExp<C> literalExp) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = null;
    res[1] = literalExp;
    return res;
  }

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitPropertyCallExp(PropertyCallExp<C, P> callExp) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = callExp;
    res[1] = null;
    return res;
  }

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitVariableExp(VariableExp<C, PM> v) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = v;
    res[1] = null;
    return res;
  }

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitStringLiteralExp(StringLiteralExp<C> literalExp) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = null;
    res[1] = literalExp;
    return res;
  }

  @SuppressWarnings("unchecked")
  @Override
  public OCLExpression<C>[] visitBooleanLiteralExp(
      BooleanLiteralExp<C> literalExp) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = null;
    res[1] = literalExp;
    return res;
  }

  @Override
  public OCLExpression<C>[] visitTypeExp(TypeExp<C> t) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = t;
    res[1] = null;
    return res;
  }

  @SuppressWarnings("unchecked")
  protected OCLExpression<C>[] handleOperationCallExp(
      OperationCallExp<C, O> callExp, OCLExpression<C>[] sourceResult,
      List<OCLExpression<C>[]> argumentResults) {
    OCLExpression<C>[] res = new OCLExpression[2];

    int opCode = callExp.getOperationCode();
    switch (opCode) {
    case PredefinedType.TIMES:
    case PredefinedType.DIVIDE:
      if (isExpressionValiable(callExp)) {
        res[0] = callExp;
        res[1] = null;
        break;
      } else {
        res[0] = null;
        res[1] = callExp;
        break;
      }
    case PredefinedType.PLUS:
    case PredefinedType.MINUS:
      res[0] = createLeftExpression(sourceResult[0], argumentResults.get(0)[0],
          callExp);
      res[1] = createLeftExpression(sourceResult[1], argumentResults.get(0)[1],
          callExp);
      break;

    default:
      res = new OCLExpression[2];
      if (callExp.getSource().getType() instanceof PrimitiveType) {
        res[0] = null;
        res[1] = callExp;
      } else {
        res[0] = callExp;
        res[1] = null;
      }
      break;
    }
    return res;

  }

  protected OCLExpression<C>[] handleIterateExp(IterateExp<C, PM> callExp,
      OCLExpression<C>[] sourceResult, OCLExpression<C>[] variableResults,
      OCLExpression<C>[] resultResult, OCLExpression<C>[] bodyResult) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = (OCLExpression<C>) EcoreUtil.copy(callExp);
    res[1] = null;
    return res;
  };

  protected OCLExpression<C>[] handlePropertyCallExp(
      PropertyCallExp<C, P> callExp,
      OCLExpression<C>[] sourceResult,
      List<OCLExpression<C>[]> qualifierResults) {
    OCLExpression<C>[] res = new OCLExpression[2];
    res[0] = (OCLExpression<C>) EcoreUtil.copy(callExp);
    res[1] = null;
    return res;
  };

  @SuppressWarnings( { "unchecked", "restriction" })
  private OCLExpression<C> createLeftExpression(OCLExpression<C> left,
      OCLExpression<C> right, OperationCallExp<C, O> baseExpression) {
    int opCode = baseExpression.getOperationCode();

    if (right == null)
      return left;

    if (left == null) {
      if (right == null)
        return null;
      else {
        switch (opCode) {
        case PredefinedType.PLUS:
          return right;
        default:
          IntegerLiteralExp<C> literal = OCLFactoryImpl.INSTANCE
              .createIntegerLiteralExp();
          literal.setType(baseExpression.getType());
          literal.setIntegerSymbol(0);
          OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
              .copy(baseExpression);
          result.setSource(literal);
          result.getArgument().clear();
          result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
          return result;
        }

      }
    } else { // left != null && right!= null
      OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
          .copy(baseExpression);
      result.setSource((OCLExpression<C>) EcoreUtil.copy(left));
      result.getArgument().clear();
      result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
      return result;
    }
  };

  @SuppressWarnings("unchecked")
  private boolean isExpressionValiable(OperationCallExp<C, O> exp) {
    if (exp.getSource() instanceof OperationCallExp) {
      if (isExpressionValiable((OperationCallExp<C, O>) exp.getSource()))
        return true;
      else if (exp.getArgument().get(0) instanceof OperationCallExp)
        return isExpressionValiable((OperationCallExp<C, O>) exp.getArgument()
            .get(0));
      else if (exp.getArgument().get(0) instanceof LiteralExp)
        return false;
    }

    if (false == exp.getSource() instanceof LiteralExp)
      return true;
    else {
      if (exp.getArgument().get(0) instanceof OperationCallExp)
        return isExpressionValiable((OperationCallExp<C, O>) exp.getArgument()
            .get(0));
      else if (exp.getArgument().get(0) instanceof LiteralExp)
        return false;
      else
        return true;
    }
  }

}
